package com.bite.friend.Service.ServiceImpl;

import Constants.RedisConstant;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bite.friend.DTO.ExamListDTO;
import com.bite.friend.Mapper.ExamMapper;
import com.bite.friend.Mapper.ExamQuestionMapper;
import com.bite.friend.Mapper.UserExamMapper;
import com.bite.friend.Service.ExamService;
import com.bite.friend.Service.UserExamService;
import com.bite.friend.VO.ExamListVO;
import com.bite.friend.doamin.Exam;
import com.bite.friend.doamin.ExamQuestion;
import com.bite.friend.doamin.UserExam;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import domain.MyThreadLocal;
import domain.R;
import domain.TableDataInfo;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ExamServiceImpl implements ExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private StringRedisTemplate redis;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private UserExamServiceImpl userExamService;


    @Autowired
    private ExamQuestionMapper examQuestionMapper;
    @Override
    public TableDataInfo list(ExamListDTO examListDTO) {
        PageHelper.startPage(examListDTO.getPage(), examListDTO.getPageSize());
        List<ExamListVO> list= examMapper.list(examListDTO);
        if (list == null || list.size() == 0){
            return TableDataInfo.empty();
        }
        Page page = (Page)list;
        return TableDataInfo.success(list, page.getTotal());
    }

    @Override
    public TableDataInfo listRedis(ExamListDTO examListDTO) {
        //先判断redis中是否存在数据，如果不存在，则从数据库中获取
        String listKey = "";
        if (examListDTO.getType() == 0){
            listKey = RedisConstant.UNFINISHED_EXAM_LIST;
        }else if (examListDTO.getType() == 1){
            listKey = RedisConstant.HISTORY_EXAM_LIST;
        }

        Long listSize = redis.opsForList().size(listKey);

        if (listSize == null || listSize == 0){
            //接着需要将数据刷新到redis中
            refreshRedis(examListDTO);
        }

        //如果redis中存在数据，则直接返回数据
        //首先从redis列表中获取全部数据
        int start = (examListDTO.getPage() - 1) * examListDTO.getPageSize();
        int end = start + examListDTO.getPageSize() - 1;
        List<String> listExamId = redis.opsForList().range(listKey, start, end);

        //带上前缀
        List<String> stringList = listExamId.stream().map(e -> RedisConstant.EXAM_DETAIL_KEY + e).toList();
        List<String> examDetail = redis.opsForValue().multiGet(stringList);

        List<Exam> examList = examDetail.stream().map(e -> JSONUtil.toBean(e, Exam.class)).toList();
        //继续判断未完成竞赛的列表的数量与详情列表数量是否一致，如果不一致，则需要刷新redis

        List<Exam> examList1 = new ArrayList<>();
        if (StrUtil.isNotEmpty(examListDTO.getStartTime()) && StrUtil.isNotEmpty(examListDTO.getEndTime())) {
            // 使用 OffsetDateTime 解析带时区的日期时间字符串
            DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

            OffsetDateTime startDateTime = OffsetDateTime.parse(examListDTO.getStartTime(), formatter);
            OffsetDateTime endDateTime = OffsetDateTime.parse(examListDTO.getEndTime(), formatter);

            for (Exam e : examList) {
                // 将 Exam 的 startTime 和 endTime 转换为 OffsetDateTime 进行比较
                OffsetDateTime examStart = e.getStartTime().atOffset(ZoneOffset.UTC);
                OffsetDateTime examEnd = e.getEndTime().atOffset(ZoneOffset.UTC);

                if (examStart.isAfter(startDateTime) && examEnd.isBefore(endDateTime)) {
                    examList1.add(e);
                }
            }
            examList = examList1;
            listSize = (long) examList1.size();
        }
        List<ExamListVO> examListVO = BeanUtil.copyToList(examList, ExamListVO.class);
        //判断该用户再这些竞赛列表中是否有已经参赛
        String userId = MyThreadLocal.get();
        isTakeExam(examListVO,userId);
        return TableDataInfo.success(examListVO, listSize);
    }

    @Override
    public R<String> getExamQuestionId(long examId) {
        String key = RedisConstant.EXAM_QUESTION_ID_KEY + examId;
        //先判断redis中是否存在数据
        Long size = redis.opsForList().size(key);
        if (size == null || size == 0){
            //刷新redis中的存储数据
            refreshRedisExamQuestionIds(examId);
        }
        //获取redis列表中第一个元素
        String questionId = redis.opsForList().index(key, 0);
        return R.success(questionId);
    }

    @Override
    public R<String> getPreviousQuestionId(long examId, long questionId) {
        String key = RedisConstant.EXAM_QUESTION_ID_KEY + examId;
        //先判断当前redis中是否存在数据
        Long size = redis.opsForList().size(key);
        if (size == null || size <= 0){
            refreshRedisExamQuestionIds(examId);
        }
        //先判断当前examId的index
        long index = redis.opsForList().indexOf(key, String.valueOf(questionId));
        if (index == 0){
            throw new RuntimeException("当前题目已经到顶了!");
        }
        //获取前一个index对应的值
        long preIndex = index - 1;
        String qId = redis.opsForList().index(key, preIndex);
        return R.success(qId);
    }

    @Override
    public R<String> getNextQuestionId(long examId, long questionId) {
        String key = RedisConstant.EXAM_QUESTION_ID_KEY + examId;
        Long size = redis.opsForList().size(key);
        if (size == null || size <= 0){
            refreshRedisExamQuestionIds(examId);
        }
        long index = redis.opsForList().indexOf(key, String.valueOf(questionId));
        if (index == size - 1){
            throw new RuntimeException("当前题目已经到底了!");
        }
        String nextQuestionId = redis.opsForList().index(key, index + 1);
        return R.success(nextQuestionId);
    }

    private void refreshRedisExamQuestionIds(long examId) {
        List<ExamQuestion> examQuestions = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .orderByAsc(ExamQuestion::getQuestionOrder)
                .eq(ExamQuestion::getExamId, examId));
        if (examQuestions == null || examQuestions.size() == 0){
            throw new RuntimeException("该竞赛没有题目,请联系管理员");
        }
        List<String> stringList = examQuestions.stream().map(e -> e.getQuestionId().toString()).toList();
        //将数据刷新到redis中
        redis.opsForList().rightPushAll(RedisConstant.EXAM_QUESTION_ID_KEY + examId, stringList);
    }

    private void isTakeExam(List<ExamListVO> examListVO,String userId) {
        //获取当前登录用户的id

        if (userId == null){
            return;
        }
        //获取该用户的所有竞赛列表
        List<Long> examIdList = getAllExamList(userId);
        if (examIdList == null || examIdList.size() == 0){
            return;
        }
        for (ExamListVO e : examListVO) {
            if (examIdList.contains(e.getExamId())){
                e.setEnter(true);
            }
        }
    }

    private List<Long> getAllExamList(String userId) {
        // 检查 userId 是否为 null
        if (userId == null || userId.isEmpty()) {
            return null;
        }

        // 从 Redis 获取考试 ID 列表
        List<String> examIdList = redis.opsForList().range(RedisConstant.USER_EXAM_LIST + userId, 0, -1);

        if (examIdList == null || examIdList.isEmpty()) {
            // 如果 Redis 中没有数据，则从数据库查询
            List<UserExam> userExams = userExamMapper.selectList(new LambdaQueryWrapper<UserExam>()
                    .eq(UserExam::getUserId, Long.valueOf(userId)));

            // 将 UserExam 转换为 Exam 对象列表，并刷新 Redis
            List<Long> collect = userExams.stream()
                    .map(UserExam::getExamId)
                    .toList();

            List<Exam> examList = BeanUtil.copyToList(userExams, Exam.class);
            userExamService.refreshRedis(Long.valueOf(userId), examList);

            return collect;
        }

        // 过滤掉 examIdList 中的 null 值，并转换为 Long 类型
        return examIdList.stream()
                .filter(Objects::nonNull) // 过滤掉 null 值
                .map(Long::valueOf)      // 转换为 Long 类型
                .toList();
    }


    //刷新redis
    private void refreshRedis(ExamListDTO examListDTO){

        List<Exam> examList = new ArrayList<>();
        //从数据库中查询数据
        //1.0查询未结束的竞赛
        if (examListDTO.getType() == 0){
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .eq(Exam::getStatus, 1)
                    .gt(Exam::getEndTime, LocalDateTime.now())
                    .orderByDesc(Exam::getCreateTime));
        }else if (examListDTO.getType() == 1){
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .eq(Exam::getStatus, 1)
                    .le(Exam::getEndTime, LocalDateTime.now())
                    .orderByDesc(Exam::getCreateTime));
        }

        if (CollUtil.isEmpty(examList)){
            return;
        }

        String keyList = "";
        if (examListDTO.getType() == 0){
            keyList = RedisConstant.UNFINISHED_EXAM_LIST;
        }else if (examListDTO.getType() == 1){
            keyList = RedisConstant.HISTORY_EXAM_LIST;
        }
        if (StrUtil.isEmpty(keyList)){
            log.info("刷新redis过程中，获取到keyList为空");
            return;
        }

        //在刷新id列表之前，需要先将之前的数据删除
        redis.delete(keyList);

        //获取到数据之后，将数据刷新到redis中
        List<String> listId = examList.stream().map(e -> e.getExamId().toString()).toList();
        redis.opsForList().rightPushAll(keyList, listId);
        Map<String, String> map =
                examList.stream().collect(Collectors.toMap(e -> RedisConstant.EXAM_DETAIL_KEY + e.getExamId(),JSONUtil::toJsonStr));
        redis.opsForValue().multiSet(map);
    }
}
